package com.mrx.commons.task.logger;

import com.mrx.commons.utils.reflect.InvocationHandlerHelper;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.helpers.MessageFormatter;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * @author Mr.X
 * @since 2024-01-12 15:47
 **/
@SuppressWarnings("unused")
public class TaskLogger implements InvocationHandlerHelper, ITaskLogger {

    private final String name;

    private final ByteArrayOutputStream OUTPUT_STREAM = new ByteArrayOutputStream();

    private final PrintStream LOG_STREAM = new PrintStream(OUTPUT_STREAM, true, StandardCharsets.UTF_8);

    private final Map<String, Method> PROXY_METHODS;

    @SneakyThrows
    private TaskLogger(String name) {
        this.name = name;
        PROXY_METHODS = Arrays.stream(TaskLogger.class.getDeclaredMethods())
                .filter(it -> Modifier.isPrivate(it.getModifiers()))
                .collect(Collectors.toMap(this::getKey, Function.identity()));
    }

    public static Logger getLogger(Class<?> clazz) {
        ClassLoader classLoader = clazz.getClassLoader();
        Class<?>[] interfaces = {Logger.class, Supplier.class, ITaskLogger.class};
        return (Logger) Proxy.newProxyInstance(classLoader, interfaces, new TaskLogger(clazz.getName()));
    }

    @Override
    public String get() {
        String result = OUTPUT_STREAM.toString();
        OUTPUT_STREAM.reset();
        return result;
    }

    @Override
    public void printStackTrace(Throwable t) {
        t.printStackTrace(LOG_STREAM);
    }

    @Override
    public Object invoke(Method method, Object[] args) throws Throwable {
        if (method.getDeclaringClass().equals(Logger.class)) {
            String key = getKey(method);
            Method proxyMethod = PROXY_METHODS.get(key);
            if (proxyMethod != null) {
                return proxyMethod.invoke(this, args);
            }
        }
        return method.invoke(this, args);
    }

    private String getName() {
        return name;
    }

    private boolean isTraceEnabled() {
        return true;
    }

    private void trace(String msg) {
        LOG_STREAM.println(msg);
    }

    private void trace(String format, Object arg) {
        LOG_STREAM.println(MessageFormatter.format(format, arg).getMessage());
    }

    private void trace(String format, Object arg1, Object arg2) {
        LOG_STREAM.println(MessageFormatter.format(format, arg1, arg2).getMessage());
    }

    private void trace(String format, Object... arguments) {
        LOG_STREAM.println(MessageFormatter.arrayFormat(format, arguments).getMessage());
    }

    private void trace(String msg, Throwable t) {
        LOG_STREAM.println(MessageFormatter.format(msg, t).getMessage());
    }

    private boolean isTraceEnabled(Marker marker) {
        return true;
    }

    private void trace(Marker marker, String msg) {
        throw new UnsupportedOperationException("暂不支持");
    }

    private void trace(Marker marker, String format, Object arg) {
        throw new UnsupportedOperationException("暂不支持");
    }

    private void trace(Marker marker, String format, Object arg1, Object arg2) {
        throw new UnsupportedOperationException("暂不支持");
    }

    private void trace(Marker marker, String format, Object... argArray) {
        throw new UnsupportedOperationException("暂不支持");
    }

    private void trace(Marker marker, String msg, Throwable t) {
        throw new UnsupportedOperationException("暂不支持");
    }

    private String getKey(Method method) {
        String methodName = method.getName()
                .replace("trace", "proxy")
                .replace("debug", "proxy")
                .replace("info", "proxy")
                .replace("warn", "proxy")
                .replace("error", "proxy");
        if (methodName.contains("Enabled")) {
            methodName = "proxy";
        }
        return String.format("%s-%s", methodName, Arrays.toString(method.getParameterTypes()));
    }

}
